home *** CD-ROM | disk | FTP | other *** search
/ Cream of the Crop 1 / Cream of the Crop 1.iso / PROGRAM / ZTIMER11.ARJ / PZTIMER.ASM < prev    next >
Assembly Source File  |  1992-04-21  |  14KB  |  520 lines

  1. ;****************************************************************************
  2. ;*
  3. ;*                            Precision Zen Timer
  4. ;*
  5. ;*                               From the book
  6. ;*                         "Zen of Assembly Language"
  7. ;*                            Volume 1, Knowledge
  8. ;*
  9. ;*                             by Michael Abrash
  10. ;*
  11. ;*                      Modifications by Kendall Bennett
  12. ;*
  13. ;* Filename:    $RCSfile: pztimer.asm $
  14. ;* Version:        $Revision: 1.4 $
  15. ;*
  16. ;* Language:    8086 Assembler
  17. ;* Environment:    IBM PC (MS DOS)
  18. ;*
  19. ;* Description:    Uses the 8253 timer to time the performance of code that
  20. ;*                takes less than about 54 milliseconds to execute, with a
  21. ;*                resolution of better than 10 microseconds.
  22. ;*
  23. ;*    Externally 'C' callable routines:
  24. ;*
  25. ;*    PZTimerOn:        Starts the Zen timer, with interrupts disabled.
  26. ;*
  27. ;*    PZTimerOff:        Stops the Zen timer, saves the timer count, times the
  28. ;*                    overhead code, and restores interrupts to the state they
  29. ;*                    were in when ZTimerOn was called.
  30. ;*
  31. ;*    PZTimerReport:    Prints the net time that passed between starting and
  32. ;*                    stopping the timer.
  33. ;*
  34. ;*    PZTimerCount:    Returns an unsigned long integer representing the timed
  35. ;*                    count in microseconds. If the timer overflowed,
  36. ;*                    PZTimerCount() returns the value 0xFFFFFFFF (which is
  37. ;*                    not a valid timer count).
  38. ;*
  39. ;*    Note:    If longer than about 54 ms passes between ZTimerOn and ZTimerOff
  40. ;*            calls, the timer turns over and the count is inaccurate. When
  41. ;*            this happens, an error message is displayed instead of a count.
  42. ;*            The long period Zen timer should be used in such cases.
  43. ;*
  44. ;*    Note:    Interrupts "MUST" be left off between calls to ZTimerOn and
  45. ;*            ZTimerOff for accurate timing and for detection of timer
  46. ;*            overflow.
  47. ;*
  48. ;*    Note:    These routines can introduce slight inaccuracies into the
  49. ;*            system clock count for each code section timed even if
  50. ;*            timer 0 doesn't overflow. If timer 0 does overflow, the
  51. ;*            system clock can become slow by virtually any amount of
  52. ;*            time, since the system clock can't advance while the
  53. ;*            precision timer is timing. Consequently, it's a good idea
  54. ;*            to reboot at the end of each timing session. (The
  55. ;*            battery-backed clock, if any, is not affected by the Zen
  56. ;*            timer.)
  57. ;*
  58. ;*  All registers and all flags except the interrupt flag, are preserved
  59. ;*    by all routines. Interrupts are enabled and then disabled by ZTimerOn,
  60. ;*    and are restored by ZTimerOff to the state they were in when ZTimerOn
  61. ;*    was called.
  62. ;*
  63. ;* $Id: pztimer.asm 1.4 92/01/27 21:39:30 kjb release $
  64. ;*
  65. ;* Revision History:
  66. ;* -----------------
  67. ;*
  68. ;* $Log:    pztimer.asm $
  69. ;* Revision 1.4  92/01/27  21:39:30  kjb
  70. ;* Converted to a memory model independant library, and released to the
  71. ;* public.,
  72. ;* 
  73. ;* Revision 1.3  91/12/31  19:34:30  kjb
  74. ;* Changed include file directories.
  75. ;* 
  76. ;* Revision 1.2  91/11/16  17:11:15  kjb
  77. ;* Modified to return a long integer count rather than a string.
  78. ;* 
  79. ;* Revision 1.1  91/11/14  17:17:29  kjb
  80. ;* Initial revision
  81. ;* 
  82. ;****************************************************************************
  83.  
  84.         IDEAL
  85.  
  86. INCLUDE "model.mac"                ; Memory model macros
  87.  
  88. header    pztimer                    ; Set up memory model
  89.  
  90. ;****************************************************************************
  91. ;
  92. ; Equates used by precision Zen Timer
  93. ;
  94. ;****************************************************************************
  95.  
  96. ; Base address of 8253 timer chip
  97.  
  98. BASE_8253        =        40h
  99.  
  100. ; The address of the timer 0 count registers in the 8253
  101.  
  102. TIMER_0_8253    =        BASE_8253 + 0
  103.  
  104. ; The address of the mode register in the 8253
  105.  
  106. MODE_8253        =        BASE_8253 + 3
  107.  
  108. ; The address of Operation Command Word 3 in the 8259 programmable
  109. ; interrupt controller (PIC) (write only, and writable only when
  110. ; bit 4 of the byte written to this address is 0 and bit 3 is 1).
  111.  
  112. OCW3            =        20h
  113.  
  114. ; The address of the Interrupt Request register in the 8259 PIC
  115. ; (read only, and readable only when bit 1 of OCW3 = 1 and bit 0 of
  116. ; OCW3 = 0).
  117.  
  118. IRR                =        20h
  119.  
  120. ; Macro to emulate a POPF instruction in order to fix the bug in some
  121. ; 80286 chips which allows interrupts to occur during a POPF even when
  122. ; interrupts are disabled.
  123.  
  124. macro    MPOPF
  125.         local    p1,p2
  126.         jmp        short p2
  127. p1:        iret                    ; Jump to pushed address & pop flags
  128. p2:        push    cs                ; construct far return address to
  129.         call    p1                ;  the next instructionz
  130. endm
  131.  
  132. ; Macro to delay briefly to ensure that enough time has elapsed between
  133. ; successive I/O accesses so that the device being accessed can respond
  134. ; to both accesses even on a very fast PC.
  135.  
  136. macro    DELAY
  137.         jmp        $+2
  138.         jmp        $+2
  139.         jmp        $+2
  140. endm
  141.  
  142. begcodeseg  pztimer             ; Start of code segment
  143.  
  144. OriginalFlags    db    ?            ; Storage for upper byte of FLAGS register
  145.                                 ;  when ZTimerOn was called
  146. TimedCount        dw    ?            ; Timer 0 count when the time was stopped
  147.  
  148. ReferenceCount    dw    ?            ; Number of counts required to execute timer
  149.                                 ;  overhead code
  150. OverflowFlag    db    ?            ; Used to indicate whether the timer
  151.                                 ;  overflowed during the timing interval
  152.  
  153. ; String printed to report results.
  154.  
  155. label    OutputStr byte
  156.         db        0dh, 0ah, 'Timed count: ', 5 dup (?)
  157. label    ASCIICountEnd byte
  158.         db        ' microseconds', 0ah, 0dh
  159.         db        '$'
  160.  
  161. ; String printed to report timer overflow.
  162.  
  163. label    OverflowStr byte
  164.         db        0dh,0ah
  165.         db        '***************************************************',0dh,0ah
  166.         db        '* The timer overflowed, so the interval timed was *',0dh,0ah
  167.         db        '* too long for the precision timer to measure.    *',0dh,0ah
  168.         db        '* Please perform the timing test again with the   *',0dh,0ah
  169.         db        '* long-period timer.                              *',0dh,0ah
  170.         db        '***************************************************',0dh,0ah
  171.         db        '$'
  172.  
  173. ;----------------------------------------------------------------------------
  174. ; void PZTimerOn(void);
  175. ;----------------------------------------------------------------------------
  176. ; Starts the Precision Zen timer counting.
  177. ;----------------------------------------------------------------------------
  178. procfar        _PZTimerOn
  179.  
  180. ; Save the context of the program being timed
  181.  
  182.         push    ax
  183.         pushf
  184.         pop        ax                    ; Get flags so we can keep interrupts
  185.                                     ;  off when leaving this routine
  186.         mov        [OriginalFlags],ah    ; remember the state of the int flag
  187.         and        ah,0fdh                ; Set pushed interrupt flag to 0
  188.         push    ax
  189.  
  190. ; Turn on interrupts, so the timer interrupt can occur if it's pending.
  191.  
  192.         sti
  193.  
  194. ; Set the timer 0 of the 8253 to mode 2 (divide-by-N), to cause
  195. ; linear counting rather than count-by-two counting. Also leaves
  196. ; the 8253 waiting for the initial timer 0 count to be loaded.
  197.  
  198.         mov        al,00110100b        ; mode 2
  199.         out        MODE_8253,al
  200.  
  201. ; Set the timer count to 0, so we know we won't get another timer
  202. ; interrupt right away. Note: this introduces and inaccuracy of up to 54 ms
  203. ; in the system clock count each time it is executed.
  204.  
  205.         DELAY
  206.         sub        al,al
  207.         out        TIMER_0_8253,al        ; lsb
  208.         DELAY
  209.         out        TIMER_0_8253,al        ; msb
  210.  
  211. ; Wait before clearing interrupts to allow the interrupt generated when
  212. ; switching from mode 3 to mode 2 to be recognised. The delay must be at
  213. ; least 210ns long to allow time for that interrupt to occur. Here, 10
  214. ; jumps are used for the delay to ensure that the delay time will be more
  215. ; than enough even on a very fast PC.
  216.  
  217.         rept    10
  218.         jmp        $+2
  219.         endm
  220.  
  221. ; Disable interrupts to get an accurate count.
  222.  
  223.         cli
  224.  
  225. ; Set the timer count to 0 again to start the timing interval.
  226.  
  227.         mov        al,00110100b        ; set up to load initial
  228.         out        MODE_8253,al        ; timer count
  229.         DELAY
  230.         sub        al,al
  231.         out        TIMER_0_8253,al        ; load count lsb
  232.         DELAY
  233.         out        TIMER_0_8253,al        ; load count msb
  234.  
  235. ; Restore the context and return.
  236.  
  237.         MPOPF                        ; keeps interrupts off
  238.         pop        ax
  239.         ret
  240.  
  241. procend        _PZTimerOn
  242.  
  243. ;----------------------------------------------------------------------------
  244. ; void PZTimerOff(void);
  245. ;----------------------------------------------------------------------------
  246. ; Stops the Precision Zen timer and save count.
  247. ;----------------------------------------------------------------------------
  248. procfar        _PZTimerOff
  249.  
  250. ; Save the context of the program being timed
  251.  
  252.         push    ax
  253.         push    cx
  254.         pushf
  255.  
  256. ; Latch the count.
  257.  
  258.         mov        al,00000000b        ; latch timer 0
  259.         out        MODE_8253,al
  260.  
  261. ; See if the timer has overflowed by checking the 8259 for a pending
  262. ; timer interrupt.
  263.  
  264.         mov        al,00001010b        ; OCW3, set up to read
  265.         out        OCW3,al                ;  Interrupt request register
  266.         DELAY
  267.         in        al,IRR                ; read Interrupt Request Register
  268.         and        al,1                ; set AL to 1 if IRQ0 (the timer
  269.                                     ;  interrupt) is pending
  270.         mov        [OverflowFlag],al    ; Store the timer overflow status
  271.  
  272. ; Allow interrupts to happen again.
  273.  
  274.         sti
  275.  
  276. ; Read out the count we latched earlier.
  277.  
  278.         in        al,TIMER_0_8253        ; least significant byte
  279.         DELAY
  280.         mov        ah,al
  281.         in        al,TIMER_0_8253        ; most significant byte
  282.         xchg    ah,al
  283.         neg        ax                    ; Convert from countdown remaining
  284.                                     ;  to elapsed count
  285.         mov        [TimedCount],ax
  286.  
  287. ; Time a zero-length code fragment, to get a reference count for how
  288. ; much overhead this routine has. Time it 16 times and average it, for
  289. ; accuracy, rounding the result.
  290.  
  291.         mov        [ReferenceCount],0
  292.         mov        cx,16
  293.         cli                            ; interrupts off to allow a precise
  294.                                     ;  reference count
  295. @@RefLoop:
  296.         call    ReferenceTimerOn
  297.         call    ReferenceTimerOff
  298.         loop    @@RefLoop
  299.         sti
  300.         add        [ReferenceCount],8    ; total + (0.5 * 16)
  301.         mov        cl,4
  302.         shr        [ReferenceCount],cl    ; (total) / 16 + 0.5
  303.  
  304. ; Restore original interrupt state.
  305.  
  306.         pop        ax                    ; Retrieve flags when called
  307.         mov        ch,[OriginalFlags]    ; get back the original upper
  308.                                     ;  byte of the FLAGS register
  309.         and        ch,not 0fdh            ; only care about original interrupt
  310.                                     ;  flag...
  311.         and        ah,0fdh                ; ...keep all other flags in
  312.         or        ah,ch                ;  their current condition
  313.         push    ax                    ; Prepare flags to be popped
  314.  
  315. ; Restore the context of the program being timed and return to it.
  316.  
  317.         MPOPF                        ; restore the flags with the
  318.                                     ;  original interrupt state
  319.         pop        cx
  320.         pop        ax
  321.         ret
  322.  
  323. procend        _PZTimerOff
  324.  
  325. ;----------------------------------------------------------------------------
  326. ; ReferenceTimerOn
  327. ;----------------------------------------------------------------------------
  328. ; Called by PZTimerOff to start timer for overhead measurements.
  329. ;----------------------------------------------------------------------------
  330. proc        ReferenceTimerOn far
  331.  
  332. ; Save the context of the program being timed
  333.  
  334.         push    ax
  335.         pushf                        ; Interrupts are already off
  336.  
  337. ; Set the timer 0 of the 8253 to mode 2 (divide-by-N), to cause
  338. ; linear counting rather than count-by-two counting.
  339.  
  340.         mov        al,00110100b        ; set up to load
  341.         out        MODE_8253,al        ; timer count
  342.         DELAY
  343.  
  344. ; Set the timer count to 0
  345.  
  346.         sub        al,al
  347.         out        TIMER_0_8253,al        ; load count lsb
  348.         DELAY
  349.         out        TIMER_0_8253,al        ; load count msb
  350.  
  351. ; Restore the context and return.
  352.  
  353.         MPOPF
  354.         pop        ax
  355.         ret
  356.  
  357. procend        ReferenceTimerOn
  358.  
  359. ;----------------------------------------------------------------------------
  360. ; ReferenceTimerOff
  361. ;----------------------------------------------------------------------------
  362. ; Called by PZTimerOff to stop timer and add result to ReferenceCount
  363. ; for overhead measurements.
  364. ;----------------------------------------------------------------------------
  365. procfar        ReferenceTimerOff far
  366.  
  367. ; Save the context of the program being timed
  368.  
  369.         push    ax
  370.         push    cx
  371.         pushf
  372.  
  373. ; Latch the count and read it.
  374.  
  375.         mov        al,00000000b        ; latch timer 0
  376.         out        MODE_8253,al
  377.         DELAY
  378.         in        al,TIMER_0_8253        ; least significant byte
  379.         DELAY
  380.         mov        ah,al
  381.         in        al,TIMER_0_8253        ; most significant byte
  382.         xchg    ah,al
  383.         neg        ax                    ; Convert from countdown remaining
  384.                                     ;  to elapsed count
  385.         add        [ReferenceCount],ax
  386.  
  387. ; Restore the context of the program being timed and return to it.
  388.  
  389.         MPOPF                        ; restore the flags with the
  390.                                     ;  original interrupt state
  391.         pop        cx
  392.         pop        ax
  393.         ret
  394.  
  395. procend        ReferenceTimerOff
  396.  
  397. ;----------------------------------------------------------------------------
  398. ; void PZTimerReport(void);
  399. ;----------------------------------------------------------------------------
  400. ; Report timing results found.
  401. ;----------------------------------------------------------------------------
  402. procfar        _PZTimerReport
  403.  
  404.         pushf
  405.         push    ax
  406.         push    bx
  407.         push    cx
  408.         push    dx
  409.         push    si
  410.         push    ds
  411.  
  412.         push    cs                    ; DOS functions require that DS point
  413.         pop        ds                    ;  to text to be displayed on the screen
  414.  
  415. if codesize
  416.         ASSUME    ds:pztimer_TEXT
  417. else
  418.         ASSUME    ds:_TEXT
  419. endif
  420.  
  421. ; Check for timer 0 overflow.
  422.  
  423.         cmp        [OverflowFlag],0
  424.         jz        @@GoodCount
  425.         mov        dx,offset OverflowStr
  426.         mov        ah,9
  427.         int        21h
  428.         jmp        short @@Done
  429.  
  430. ; Convert net count to decimal ASCII in microseconds.
  431.  
  432. @@GoodCount:
  433.         mov        ax,[TimedCount]
  434.         sub        ax,[ReferenceCount]
  435.         mov        si,offset ASCIICountEnd-1
  436.  
  437. ; Convert count to microseconds by multiplying by 0.8381
  438.  
  439.         mov        dx,8381
  440.         mul        dx
  441.         mov        bx,10000
  442.         div        bx                    ; * 0.8381 = * 8381 / 10000
  443.  
  444. ; Convert time in microseconds to 5 decimal ASCII digits.
  445.  
  446.         mov        bx,10
  447.         mov        cx,5
  448.  
  449. @@CTSLoop:
  450.         sub        dx,dx
  451.         div        bx
  452.         add        dl,'0'
  453.         mov        [si],dl
  454.         dec        si
  455.         loop    @@CTSLoop
  456.  
  457. ; Print the results.
  458.  
  459.         mov        ah,9
  460.         mov        dx,offset OutputStr
  461.         int        21h
  462.  
  463. @@Done:
  464.         pop        ds
  465.         pop        si
  466.         pop        dx
  467.         pop        cx
  468.         pop        bx
  469.         pop        ax
  470.         MPOPF
  471.         ret
  472.  
  473.         ASSUME    ds:DGROUP
  474.  
  475. procend        _PZTimerReport
  476.  
  477. ;----------------------------------------------------------------------------
  478. ; unsigned long PZTimerCount(void);
  479. ;----------------------------------------------------------------------------
  480. ; Returns an unsigned long representing the net time in microseconds.
  481. ;----------------------------------------------------------------------------
  482. procfar        _PZTimerCount
  483.  
  484.         setupDS
  485.  
  486. ; Check for timer 0 overflow.
  487.  
  488.         cmp        [OverflowFlag],0
  489.         jz        @@GoodCount
  490.  
  491. ; The timer overflowed, so set set the count to 0xFFFFFFFF
  492.         mov        ax,0FFFFh
  493.         mov        dx,0FFFFh
  494.         jmp        short @@Done
  495.  
  496. ; Convert net count to decimal ASCII in microseconds.
  497.  
  498. @@GoodCount:
  499.         mov        ax,[TimedCount]
  500.         sub        ax,[ReferenceCount]
  501.  
  502. ; Convert count to microseconds by multiplying by 0.8381
  503.  
  504.         mov        dx,8381
  505.         mul        dx
  506.         mov        bx,10000
  507.         div        bx                    ; * 0.8381 = * 8381 / 10000
  508.         xor        dx,dx
  509.  
  510. @@Done:
  511.         restoreDS
  512.  
  513.         ret
  514.  
  515. procend        _PZTimerCount
  516.  
  517. endcodeseg    pztimer
  518.  
  519.         END                        ; End of module
  520.